/**
 * Copyright 2007 Igor Chaves Cananéa
 *
 * This file is part of PBSFW
 *
 * PBSFW is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * PBSFW is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef _STORE_H_
#define _STORE_H_

#include <deque>
#include <map>
#include <cstdarg>
#include "resetable.h"

#ifdef _INTERNAL_CLASS_
#define _TAG_CLASS_
#undef _INTERNAL_CLASS_
#endif

#include "simulatorinterface.h"

using namespace std;

namespace pbsfw {
    template<typename OBJECT>
    class Store : public Resetable {
        protected:
            deque<OBJECT> store;

        private:
            deque<pair<Process*, int> > putQueue;
            deque<pair<Process*, int> > getQueue;
            int maxAmount;
            int currentAmount;

            bool hasSpace(int amount) {
                return this->maxAmount == 0 ||
                       this->currentAmount + amount <= this->maxAmount;
            };

            void continueGet();
            void continuePut();

            void do_put(Process &p, uint amount, deque<OBJECT> &l) {
                this->currentAmount += amount;
                while (amount > 0) {
                    OBJECT obj = l.front();
                    this->store.push_back(obj);
                    l.pop_front();
                    --amount;
                }
                this->continueGet();
            }

        public:
            Store(int maxAmount = 0) : maxAmount(maxAmount), currentAmount(0) {}

            inline bool put(Process &p, OBJECT obj);
            inline bool put(Process &p, uint amount, deque<OBJECT> &l);

            inline bool get(Process &p, OBJECT &obj);
            inline bool get(Process &p, int amount, deque<OBJECT> &l);

            OBJECT front() {
                return this->store.front();
            }

            int qntQueuedObjects() const {
                return this->store.size();
            }

            bool hasObjects() const {
                return !this->store.empty();
            }

            virtual void reset() {
                this->putQueue.clear();
                this->getQueue.clear();
                this->currentAmount = 0;
                this->store.clear();
            }
    };

    template<typename OBJECT>
    class ManagedStore : public Store<OBJECT> {
        public:
            ManagedStore(int maxAmount = 0) : Store<OBJECT>(maxAmount) {}

            void reset() {
                typename deque<OBJECT>::iterator itr = this->store.begin();
                typename deque<OBJECT>::iterator end = this->store.end();

                while (itr != end) {
                    delete *itr;
                    *itr = 0;
                    ++itr;
                }
                Store<OBJECT>::reset();

            }
    };

    template<typename OBJECT>
    bool Store<OBJECT>::put(Process &p, OBJECT obj) {
        if (this->hasSpace(1)) {
            ++this->currentAmount;
            this->store.push_back(obj);
            this->continueGet();
        }
        else {
            this->putQueue.push_back(make_pair(&p, 1));
            return false;
        }
        return true;
    }

    template<typename OBJECT>
    bool Store<OBJECT>::put(Process &p, uint amount, deque<OBJECT> &l) {
        if (this->hasSpace(amount)) {
            this->do_put(p, amount, l);
        }
        else {
            this->putQueue.push_back(make_pair(&p, amount));
            return false;
        }
        return true;
    }

    template<typename OBJECT>
    bool Store<OBJECT>::get(Process &p, OBJECT &obj) {
        if (this->currentAmount > 0) {
            --this->currentAmount;
            obj = this->store.front();
            this->store.pop_front();
            this->continuePut();
        }
        else {
            this->getQueue.push_back(make_pair(&p, 1));
            return false;
        }
        return true;
    }

    template<typename OBJECT>
    bool Store<OBJECT>::get(Process &p, int amount, deque<OBJECT> &l) {
        if (this->currentAmount - amount >= 0) {
            this->currentAmount -= amount;
            while (amount > 0) {
                OBJECT obj = this->store.front();
                l.push_back(obj);
                this->store.pop_front();
                --amount;
            }
            this->continuePut();
        }
        else {
            this->getQueue.push_back(make_pair(&p, amount));
            return false;
        }
        return true;
    }

    template<typename OBJECT>
    void Store<OBJECT>::continueGet() {
        if (!this->getQueue.empty()) {
            int size = this->getQueue.size();
            int amount = 0;
            while (size > 0) {
                pair<Process *, int> p = this->getQueue.front();
                amount += p.second;
                if (this->currentAmount - amount >= 0) {
                    this->getQueue.pop_front();
                    simulator.reactivate(*p.first, simulator.now());
                    --size;
                }
                else {
                    size = 0;
                }
            }
        }
    }

    template<typename OBJECT>
    void Store<OBJECT>::continuePut() {
        if (!this->putQueue.empty()) {
            int size = this->putQueue.size();
            int amount = 0;
            while (size > 0) {
                pair<Process *, int> p = this->putQueue.front();
                amount += p.second;
                if (this->hasSpace(amount)) {
                    this->putQueue.pop_front();
                    simulator.reactivate(*p.first, simulator.now());
                    --size;
                }
                else {
                    size = 0;
                }
            }
        }
    }
};

#ifdef _TAG_CLASS_
#undef _TAG_CLASS_
#define _INTERNAL_CLASS_
#endif

#endif
